﻿//Source https://github.com/OrchardCMS/OrchardCore/tree/dev/src/OrchardCore

using Microsoft.Extensions.FileProviders.Physical;

namespace Grand.Business.Core.Interfaces.Storage;

/// <summary>
///     Represents a generic abstraction over a virtual file store.
/// </summary>
public interface IFileStore
{
    /// <summary>
    ///     Retrieves information about the given file within the file store.
    /// </summary>
    /// <param name="path">The path within the file store.</param>
    /// <returns>A <see cref="IFileStoreEntry" /> object representing the file, or <c>null</c> if the file does not exist.</returns>
    Task<IFileStoreEntry> GetFileInfo(string path);

    /// <summary>
    ///     Retrieves information about the given directory within the file store.
    /// </summary>
    /// <param name="path">The path within the file store.</param>
    /// <returns>
    ///     A <see cref="IFileStoreEntry" /> object representing the directory, or <c>null</c> if the directory does not
    ///     exist.
    /// </returns>
    IFileStoreEntry GetDirectoryInfo(string path);

    /// <summary>
    ///     Retrieves information about the given directory within the file store.
    /// </summary>
    /// <returns>
    ///     A <see cref="PhysicalDirectoryInfo" /> object representing the directory, or <c>null</c> if the directory does
    ///     not exist.
    /// </returns>
    Task<PhysicalDirectoryInfo> GetPhysicalDirectoryInfo(string directoryPath);

    /// <summary>
    ///     Enumerates the content (files and directories) in a given directory within the file store.
    /// </summary>
    /// <remarks>
    ///     Results are grouped by entry type, where directories are followed by files.
    /// </remarks>
    IList<IFileStoreEntry> GetDirectoryContent(string path = null, bool includeSubDirectories = false,
        bool listDirectories = true, bool listFiles = true);

    /// <summary>
    ///     Creates a directory in the file store if it doesn't already exist.
    /// </summary>
    /// <param name="path">The path of the directory to be created.</param>
    /// <returns><c>true</c> if the directory was created; <c>false</c> if the directory already existed.</returns>
    bool TryCreateDirectory(string path);

    /// <summary>
    ///     Rename a directory in the file store if it doesn't already exist.
    /// </summary>
    /// <param name="path">The path of the directory to be created.</param>
    /// <param name="newName">The new name of the directory</param>
    /// <returns><c>true</c> if the directory was rename; <c>false</c> if the directory already existed.</returns>
    Task<bool> TryRenameDirectory(string path, string newName);

    /// <summary>
    ///     Deletes a file in the file store if it exists.
    /// </summary>
    /// <param name="path">The path of the file to be deleted.</param>
    /// <returns><c>true</c> if the file was deleted; <c>false</c> if the file did not exist.</returns>
    Task<bool> TryDeleteFile(string path);

    /// <summary>
    ///     Deletes a directory in the file store if it exists.
    /// </summary>
    /// <param name="path">The path of the directory to be deleted.</param>
    Task<bool> TryDeleteDirectory(string path);

    /// <summary>
    ///     Renames or moves a file to another location in the file store.
    /// </summary>
    /// <param name="oldPath">The path of the file to be renamed/moved.</param>
    /// <param name="newPath">The new path of the file after the rename/move.</param>
    Task MoveFile(string oldPath, string newPath);

    /// <summary>
    ///     Creates a copy of a file in the file store.
    /// </summary>
    /// <param name="srcPath">The path of the source file to be copied.</param>
    /// <param name="dstPath">The path of the destination file to be created.</param>
    Task CopyFile(string srcPath, string dstPath);

    /// <summary>
    ///     Rename of a file in the file store.
    /// </summary>
    /// <param name="file">The source file.</param>
    /// <param name="newName">The destination file.</param>
    Task RenameFile(string file, string newName);

    /// <summary>
    ///     Creates a stream to read the contents of a file in the file store.
    /// </summary>
    /// <param name="path">The path of the file to be read.</param>
    /// <returns>
    ///     An instance of <see cref="System.IO.Stream" /> that can be used to read the contents of the file. The caller
    ///     must close the stream when finished.
    /// </returns>
    /// <exception cref="FileStoreException">Thrown if the specified file does not exist.</exception>
    Task<Stream> GetFileStream(string path);

    /// <summary>
    ///     Creates a stream to read the contents of a file in the file store.
    /// </summary>
    /// <param name="fileStoreEntry">The IFileStoreEntry to be read.</param>
    /// <returns>
    ///     An instance of <see cref="System.IO.Stream" /> that can be used to read the contents of the file. The caller
    ///     must close the stream when finished.
    /// </returns>
    /// <exception cref="FileStoreException">Thrown if the specified file does not exist.</exception>
    Task<Stream> GetFileStream(IFileStoreEntry fileStoreEntry);

    /// <summary>
    ///     Creates a new file in the file store from the contents of an input stream.
    /// </summary>
    /// <param name="path">The path of the file to be created.</param>
    /// <param name="inputStream">The stream whose contents to write to the new file.</param>
    /// <param name="overwrite">
    ///     <c>true</c> to overwrite if a file already exists; <c>false</c> to throw an exception if the
    ///     file already exists.
    /// </param>
    /// <exception cref="FileStoreException">
    ///     Thrown if the specified file already exists and <paramref name="overwrite" /> was
    ///     not set to <c>true</c>, or if the specified path exists but is not a file.
    /// </exception>
    /// <remarks>
    ///     If the specified path contains one or more directories, then those directories are
    ///     created if they do not already exist.
    /// </remarks>
    Task<string> CreateFileFromStream(string path, Stream inputStream, bool overwrite = false);

    /// <summary>
    ///     Get text from file
    /// </summary>
    /// <param name="path"></param>
    /// <returns></returns>
    Task<string> ReadAllText(string path);

    /// <summary>
    ///     Write text to file
    /// </summary>
    /// <param name="path"></param>
    /// <param name="text"></param>
    /// <returns></returns>
    Task WriteAllText(string path, string text);
}

public static class IFileStoreExtensions
{
    /// <summary>
    ///     Combines multiple path parts using the path delimiter semantics of the abstract virtual file store.
    /// </summary>
    /// <param name="fileStore">The <see cref="IFileStore" />.</param>
    /// <param name="paths">The path parts to combine.</param>
    /// <returns>The full combined path.</returns>
    public static string Combine(this IFileStore fileStore, params string[] paths)
    {
        if (paths.Length == 0)
            return null;

        var normalizedParts =
            paths
                .Select(x => fileStore.NormalizePath(x))
                .Where(x => !string.IsNullOrEmpty(x))
                .ToArray();

        var combined = string.Join("/", normalizedParts);
        if (!Path.IsPathRooted(combined)) combined = "/" + combined;
        return combined;
    }

    /// <summary>
    ///     Normalizes a path using the path delimiter semantics of the abstract virtual file store.
    /// </summary>
    /// <remarks>
    ///     Backslash is converted to forward slash and any leading or trailing slashes
    ///     are removed.
    /// </remarks>
    public static string NormalizePath(this IFileStore fileStore, string path)
    {
        return path?.Replace('\\', '/').Trim('/', ' ');
    }
}